1use std::sync::{
2 Arc, Mutex, MutexGuard, Weak,
3 atomic::{AtomicBool, AtomicU64, Ordering},
4};
5
6use crate::pp_log::{PpLog, pp_info};
7
8use crate::{
9 buffer::MediaBuffer,
10 bus::BusEvent,
11 control::ControlMsg,
12 element::{Context, Element, ElementType, Sink, element_pp_log},
13 error::Result,
14 graph::{BranchId, ElementId, GraphError, PlannedEdge, PortRef, log_topology},
15 pad::SrcPad,
16 pipeline::{ChainBuilder, DetachedBranch},
17};
18
19pub struct Tee {
36 pp_log: PpLog,
37 id: ElementId,
38 name: Arc<str>,
39 shared: Arc<TeeShared>,
40}
41
42struct TeeShared {
43 branches: Mutex<Vec<Arc<TeeBranch>>>,
44 next_pad_id: AtomicU64,
45 context: Arc<Context>,
46}
47
48struct TeeBranch {
49 id: Option<BranchId>,
50 root_id: ElementId,
51 active: AtomicBool,
52 pad: Mutex<SrcPad>,
53}
54
55fn lock_unpoisoned<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
60 match mutex.lock() {
61 Ok(guard) => guard,
62 Err(poisoned) => poisoned.into_inner(),
63 }
64}
65
66pub struct TeeBuilder {
72 tee: Tee,
73 handle: TeeHandle,
74 initial_branches: Vec<DetachedBranch>,
75}
76
77#[derive(Clone)]
84pub struct TeeHandle {
85 id: ElementId,
86 name: Arc<str>,
87 pp_log: PpLog,
91 shared: Weak<TeeShared>,
92}
93
94impl Tee {
95 fn new(name: impl Into<String>, context: Arc<Context>) -> (Self, TeeHandle) {
96 let name: Arc<str> = name.into().into();
97 let pp_log = element_pp_log(ElementType::Tee, &name, Some(&context.pipeline_id));
98 pp_info!(pp_log: &pp_log, "created");
99 let id = context.graph.reserve_element_id();
100 let shared = Arc::new(TeeShared {
101 branches: Mutex::new(Vec::new()),
102 next_pad_id: AtomicU64::new(0),
103 context,
104 });
105 (
106 Self {
107 id,
108 name: name.clone(),
109 pp_log: pp_log.clone(),
110 shared: shared.clone(),
111 },
112 TeeHandle {
113 id,
114 name,
115 pp_log,
116 shared: Arc::downgrade(&shared),
117 },
118 )
119 }
120
121 fn report_branch_error(
132 &self,
133 root_id: ElementId,
134 peer: Option<(ElementType, Arc<str>)>,
135 error: crate::error::Error,
136 ) {
137 let (element_type, name) = peer.unwrap_or((ElementType::Tee, self.name.clone()));
138 self.shared.context.bus.for_element(root_id).post(
139 &self.pp_log,
140 BusEvent::Error {
141 element_type,
142 name,
143 error,
144 },
145 );
146 }
147}
148
149impl TeeShared {
150 fn next_pad(&self, tee_name: &str) -> SrcPad {
151 let id = self.next_pad_id.fetch_add(1, Ordering::Relaxed);
152 SrcPad::new(format!("{tee_name}_src{id}"))
153 }
154}
155
156impl TeeBuilder {
157 pub fn new(name: impl Into<String>, context: Arc<Context>) -> Self {
159 let (tee, handle) = Tee::new(name, context);
160 Self {
161 tee,
162 handle,
163 initial_branches: Vec::new(),
164 }
165 }
166
167 pub fn branch(mut self, branch: DetachedBranch) -> Self {
169 self.initial_branches.push(branch);
170 self
171 }
172
173 pub fn build(self) -> Result<DetachedBranch> {
175 self.finish().map(|(branch, _handle)| branch)
176 }
177
178 pub fn build_dynamic(self) -> Result<(DetachedBranch, TeeHandle)> {
181 self.finish()
182 }
183
184 fn finish(self) -> Result<(DetachedBranch, TeeHandle)> {
185 let Self {
186 tee,
187 handle,
188 initial_branches,
189 } = self;
190 let tee_id = tee.id;
191 let tee_name = tee.name.clone();
192 let shared = tee.shared.clone();
193 let context = shared.context.clone();
194 let mut tee_branch = context.branch().to(Box::new(tee))?;
195 let mut runtime_branches = lock_unpoisoned(&shared.branches);
196
197 for branch in initial_branches {
198 let mut pad = shared.next_pad(&tee_name);
199 let from_port: Arc<str> = pad.name().into();
200 let DetachedBranch { root, plan } = branch;
201 let root_id = plan.root;
202
203 tee_branch.plan.edges.push(PlannedEdge {
204 from: PortRef {
205 element: tee_id,
206 port: from_port,
207 },
208 to: PortRef {
209 element: root_id,
210 port: "sink".into(),
211 },
212 });
213 tee_branch.plan.nodes.extend(plan.nodes);
214 tee_branch.plan.edges.extend(plan.edges);
215
216 pad.link(root);
217 runtime_branches.push(Arc::new(TeeBranch {
218 id: None,
219 root_id,
220 active: AtomicBool::new(true),
221 pad: Mutex::new(pad),
222 }));
223 }
224 drop(runtime_branches);
225
226 Ok((tee_branch, handle))
227 }
228}
229
230impl TeeHandle {
231 pub fn branch(&self) -> Option<ChainBuilder> {
238 let shared = self.shared.upgrade()?;
239 Some(shared.context.branch())
240 }
241
242 pub fn attach(&self, branch: DetachedBranch) -> Result<BranchId> {
245 let shared = self
246 .shared
247 .upgrade()
248 .ok_or(GraphError::ParentNotAttached(self.id))?;
249 let mut branches = lock_unpoisoned(&shared.branches);
250 let mut pad = shared.next_pad(&self.name);
251 let from_port: Arc<str> = pad.name().into();
252 let DetachedBranch { root, plan } = branch;
253 let root_id = plan.root;
254 let branch_id =
255 shared
256 .context
257 .graph
258 .attach_with(self.id, from_port, plan, |branch_id| {
259 pad.link(root);
260 branches.push(Arc::new(TeeBranch {
261 id: Some(branch_id),
262 root_id,
263 active: AtomicBool::new(true),
264 pad: Mutex::new(pad),
265 }));
266 Ok(())
267 })?;
268 let snapshot =
269 crate::log::enabled(crate::log::Level::Info).then(|| shared.context.graph.snapshot());
270 drop(branches);
271 if let Some(snapshot) = snapshot {
272 log_topology(&self.pp_log, "attach", &snapshot);
273 }
274 Ok(branch_id)
275 }
276
277 pub fn detach(&self, branch_id: BranchId) -> Result<()> {
281 let shared = self
282 .shared
283 .upgrade()
284 .ok_or(GraphError::BranchNotAttached(branch_id))?;
285 let mut branches = lock_unpoisoned(&shared.branches);
286 let index = branches
287 .iter()
288 .position(|branch| branch.id == Some(branch_id))
289 .ok_or(GraphError::BranchNotAttached(branch_id))?;
290 let mut removed = None;
291 shared.context.graph.detach_with(branch_id, || {
292 let branch = branches.remove(index);
293 branch.active.store(false, Ordering::Release);
294 removed = Some(branch);
295 Ok(())
296 })?;
297 let snapshot =
298 crate::log::enabled(crate::log::Level::Info).then(|| shared.context.graph.snapshot());
299 drop(branches);
300 drop(removed);
304 if let Some(snapshot) = snapshot {
305 log_topology(&self.pp_log, "detach", &snapshot);
306 }
307 Ok(())
308 }
309
310 pub fn detach_branch_containing(&self, element: ElementId) -> Result<()> {
314 let shared = self
315 .shared
316 .upgrade()
317 .ok_or(GraphError::ParentNotAttached(self.id))?;
318 let branch_id = shared
319 .context
320 .graph
321 .branch_containing(element)
322 .ok_or(GraphError::ParentNotAttached(element))?;
323 self.detach(branch_id)
324 }
325
326 pub fn sink_count(&self) -> usize {
327 self.shared
328 .upgrade()
329 .map(|shared| lock_unpoisoned(&shared.branches).len())
330 .unwrap_or(0)
331 }
332}
333
334impl Element for Tee {
335 fn name(&self) -> Arc<str> {
336 self.name.clone()
337 }
338
339 fn element_type(&self) -> ElementType {
340 ElementType::Tee
341 }
342
343 fn graph_id(&self) -> Option<ElementId> {
344 Some(self.id)
345 }
346
347 fn pp_log(&self) -> &PpLog {
348 &self.pp_log
349 }
350
351 fn pp_log_mut(&mut self) -> &mut PpLog {
352 &mut self.pp_log
353 }
354}
355
356impl Sink for Tee {
357 fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
358 let branches = lock_unpoisoned(&self.shared.branches).clone();
359 for branch in branches {
366 if !branch.active.load(Ordering::Acquire) {
367 continue;
368 }
369 let mut pad = lock_unpoisoned(&branch.pad);
370 if !branch.active.load(Ordering::Acquire) {
373 continue;
374 }
375 let peer = pad.peer_identity();
376 let outcome = pad.push(buf.clone());
377 drop(pad);
378 if let Err(error) = outcome {
379 self.report_branch_error(branch.root_id, peer, error);
380 }
381 }
382 Ok(())
383 }
384
385 fn control(&mut self, msg: ControlMsg) -> Result<()> {
386 let branches = lock_unpoisoned(&self.shared.branches).clone();
390 for branch in branches {
391 if !branch.active.load(Ordering::Acquire) {
392 continue;
393 }
394 let mut pad = lock_unpoisoned(&branch.pad);
395 if !branch.active.load(Ordering::Acquire) {
396 continue;
397 }
398 let peer = pad.peer_identity();
399 let outcome = pad.control(msg);
400 drop(pad);
401 if let Err(error) = outcome {
402 self.report_branch_error(branch.root_id, peer, error);
403 }
404 }
405 Ok(())
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use std::{
412 panic::{AssertUnwindSafe, catch_unwind},
413 sync::{
414 Barrier,
415 atomic::{AtomicBool, AtomicUsize, Ordering},
416 mpsc,
417 },
418 thread,
419 time::Duration,
420 };
421
422 use super::*;
423 use crate::{bus::Bus, graph::PipelineGraph};
424
425 fn packet() -> MediaBuffer {
426 MediaBuffer::Packet(Arc::new(ffmpeg_next::Packet::empty()))
427 }
428
429 struct CountingSink {
430 pp_log: PpLog,
431 name: &'static str,
432 count: Arc<AtomicUsize>,
433 }
434
435 impl Element for CountingSink {
436 fn name(&self) -> Arc<str> {
437 self.name.into()
438 }
439
440 fn element_type(&self) -> ElementType {
441 ElementType::Other
442 }
443
444 fn pp_log(&self) -> &PpLog {
445 &self.pp_log
446 }
447
448 fn pp_log_mut(&mut self) -> &mut PpLog {
449 &mut self.pp_log
450 }
451 }
452
453 impl Sink for CountingSink {
454 fn consume(&mut self, _buf: MediaBuffer) -> Result<()> {
455 self.count.fetch_add(1, Ordering::SeqCst);
456 Ok(())
457 }
458
459 fn control(&mut self, _msg: ControlMsg) -> Result<()> {
460 Ok(())
461 }
462 }
463
464 struct AlwaysFailSink {
465 pp_log: PpLog,
466 }
467
468 impl Element for AlwaysFailSink {
469 fn name(&self) -> Arc<str> {
470 "always-fail".into()
471 }
472
473 fn element_type(&self) -> ElementType {
474 ElementType::Other
475 }
476
477 fn pp_log(&self) -> &PpLog {
478 &self.pp_log
479 }
480
481 fn pp_log_mut(&mut self) -> &mut PpLog {
482 &mut self.pp_log
483 }
484 }
485
486 impl Sink for AlwaysFailSink {
487 fn consume(&mut self, _buf: MediaBuffer) -> Result<()> {
488 Err(crate::error::Error::Other(
489 "simulated branch failure".into(),
490 ))
491 }
492
493 fn control(&mut self, _msg: ControlMsg) -> Result<()> {
494 Ok(())
495 }
496 }
497
498 struct ControlObservingSink {
499 pp_log: PpLog,
500 name: &'static str,
501 count: Arc<AtomicUsize>,
502 fail: bool,
503 }
504
505 impl Element for ControlObservingSink {
506 fn name(&self) -> Arc<str> {
507 self.name.into()
508 }
509
510 fn element_type(&self) -> ElementType {
511 ElementType::Other
512 }
513
514 fn pp_log(&self) -> &PpLog {
515 &self.pp_log
516 }
517
518 fn pp_log_mut(&mut self) -> &mut PpLog {
519 &mut self.pp_log
520 }
521 }
522
523 impl Sink for ControlObservingSink {
524 fn consume(&mut self, _buf: MediaBuffer) -> Result<()> {
525 Ok(())
526 }
527
528 fn control(&mut self, _msg: ControlMsg) -> Result<()> {
529 self.count.fetch_add(1, Ordering::SeqCst);
530 if self.fail {
531 Err(crate::error::Error::Other(
532 "simulated control failure".into(),
533 ))
534 } else {
535 Ok(())
536 }
537 }
538 }
539
540 struct PanicOnceSink {
541 pp_log: PpLog,
542 panicked: bool,
543 successful: Arc<AtomicUsize>,
544 }
545
546 impl Element for PanicOnceSink {
547 fn name(&self) -> Arc<str> {
548 "panic-once".into()
549 }
550
551 fn element_type(&self) -> ElementType {
552 ElementType::Other
553 }
554
555 fn pp_log(&self) -> &PpLog {
556 &self.pp_log
557 }
558
559 fn pp_log_mut(&mut self) -> &mut PpLog {
560 &mut self.pp_log
561 }
562 }
563
564 impl Sink for PanicOnceSink {
565 fn consume(&mut self, _buf: MediaBuffer) -> Result<()> {
566 if !self.panicked {
567 self.panicked = true;
568 panic!("simulated downstream panic");
569 }
570 self.successful.fetch_add(1, Ordering::SeqCst);
571 Ok(())
572 }
573
574 fn control(&mut self, _msg: ControlMsg) -> Result<()> {
575 Ok(())
576 }
577 }
578
579 struct BlockingSink {
580 pp_log: PpLog,
581 entered: Option<mpsc::Sender<()>>,
582 release: mpsc::Receiver<()>,
583 }
584
585 impl Element for BlockingSink {
586 fn name(&self) -> Arc<str> {
587 "blocking".into()
588 }
589
590 fn element_type(&self) -> ElementType {
591 ElementType::Other
592 }
593
594 fn pp_log(&self) -> &PpLog {
595 &self.pp_log
596 }
597
598 fn pp_log_mut(&mut self) -> &mut PpLog {
599 &mut self.pp_log
600 }
601 }
602
603 impl Sink for BlockingSink {
604 fn consume(&mut self, _buf: MediaBuffer) -> Result<()> {
605 if let Some(entered) = self.entered.take() {
606 let _ = entered.send(());
607 }
608 let _ = self.release.recv();
609 Ok(())
610 }
611
612 fn control(&mut self, _msg: ControlMsg) -> Result<()> {
613 Ok(())
614 }
615 }
616
617 struct GraphInspectingDropSink {
618 pp_log: PpLog,
619 graph: PipelineGraph,
620 dropped: Option<mpsc::Sender<()>>,
621 }
622
623 impl Element for GraphInspectingDropSink {
624 fn name(&self) -> Arc<str> {
625 "graph-inspecting-drop".into()
626 }
627
628 fn element_type(&self) -> ElementType {
629 ElementType::Other
630 }
631
632 fn pp_log(&self) -> &PpLog {
633 &self.pp_log
634 }
635
636 fn pp_log_mut(&mut self) -> &mut PpLog {
637 &mut self.pp_log
638 }
639 }
640
641 impl Sink for GraphInspectingDropSink {
642 fn consume(&mut self, _buf: MediaBuffer) -> Result<()> {
643 Ok(())
644 }
645
646 fn control(&mut self, _msg: ControlMsg) -> Result<()> {
647 Ok(())
648 }
649 }
650
651 impl Drop for GraphInspectingDropSink {
652 fn drop(&mut self) {
653 let _ = self.graph.snapshot();
654 if let Some(dropped) = self.dropped.take() {
655 let _ = dropped.send(());
656 }
657 }
658 }
659
660 #[test]
669 fn a_failing_branch_does_not_block_its_siblings() {
670 let (bus, bus_rx) = Bus::new();
671 let graph = PipelineGraph::new();
672 let source_id = graph.add_source(ElementType::Other, "source".into());
673 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
674 let before_count = Arc::new(AtomicUsize::new(0));
675 let after_count = Arc::new(AtomicUsize::new(0));
676 let before = context
677 .branch()
678 .to(Box::new(CountingSink {
679 name: "before",
680 count: before_count.clone(),
681 pp_log: element_pp_log(ElementType::Other, "before", None),
682 }))
683 .unwrap();
684 let failing = context
685 .branch()
686 .to(Box::new(AlwaysFailSink {
687 pp_log: element_pp_log(ElementType::Other, "always-fail", None),
688 }))
689 .unwrap();
690 let after = context
691 .branch()
692 .to(Box::new(CountingSink {
693 name: "after",
694 count: after_count.clone(),
695 pp_log: element_pp_log(ElementType::Other, "after", None),
696 }))
697 .unwrap();
698 let tee_branch = TeeBuilder::new("tee", context.clone())
699 .branch(before)
700 .branch(failing)
701 .branch(after)
702 .build()
703 .unwrap();
704 let mut upstream = SrcPad::new("source_src");
705 context.attach_pad(&mut upstream, tee_branch).unwrap();
706
707 for _ in 0..3 {
708 upstream
709 .push(packet())
710 .expect("a branch failing must not surface as an error from Tee::consume");
711 }
712
713 assert_eq!(before_count.load(Ordering::SeqCst), 3);
714 assert_eq!(after_count.load(Ordering::SeqCst), 3);
715
716 drop(upstream);
717 drop(context);
718 let errors: Vec<_> = bus_rx
719 .iter()
720 .filter(|e| matches!(e, BusEvent::Error { .. }))
721 .collect();
722 assert_eq!(
723 errors.len(),
724 3,
725 "expected one Error event per failed push, not a fatal short-circuit"
726 );
727 assert!(
728 errors.iter().all(|e| matches!(
729 e,
730 BusEvent::Error { name, .. } if &**name == "always-fail"
731 )),
732 "each Error event should be attributed to the branch that actually \
733 failed, not to Tee itself — that's what lets a caller call \
734 TeeHandle::detach(branch_id) straight off the bus; got {errors:?}"
735 );
736 }
737
738 #[test]
739 fn a_failing_control_branch_does_not_block_its_siblings() {
740 let (bus, bus_rx) = Bus::new();
741 let graph = PipelineGraph::new();
742 let source_id = graph.add_source(ElementType::Other, "source".into());
743 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
744 let failing_count = Arc::new(AtomicUsize::new(0));
745 let healthy_count = Arc::new(AtomicUsize::new(0));
746 let failing = context
747 .branch()
748 .to(Box::new(ControlObservingSink {
749 name: "control-fail",
750 count: failing_count.clone(),
751 fail: true,
752 pp_log: element_pp_log(ElementType::Other, "control-fail", None),
753 }))
754 .unwrap();
755 let healthy = context
756 .branch()
757 .to(Box::new(ControlObservingSink {
758 name: "control-ok",
759 count: healthy_count.clone(),
760 fail: false,
761 pp_log: element_pp_log(ElementType::Other, "control-ok", None),
762 }))
763 .unwrap();
764 let tee_branch = TeeBuilder::new("tee", context.clone())
765 .branch(failing)
766 .branch(healthy)
767 .build()
768 .unwrap();
769 let mut upstream = SrcPad::new("source_src");
770 context.attach_pad(&mut upstream, tee_branch).unwrap();
771
772 upstream
773 .control(ControlMsg::Pause)
774 .expect("a branch control failure should be reported, not short-circuit Tee");
775
776 assert_eq!(failing_count.load(Ordering::SeqCst), 1);
777 assert_eq!(healthy_count.load(Ordering::SeqCst), 1);
778 let message = bus_rx
779 .try_recv_message()
780 .expect("the failing control branch should post an Error event");
781 assert!(matches!(
782 message.event,
783 BusEvent::Error { name, .. } if &*name == "control-fail"
784 ));
785 assert!(bus_rx.try_recv_message().is_none());
786 }
787
788 #[test]
789 fn a_poisoned_branch_pad_can_be_used_after_the_panic_is_caught() {
790 let (bus, _bus_rx) = Bus::new();
791 let graph = PipelineGraph::new();
792 let source_id = graph.add_source(ElementType::Other, "source".into());
793 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
794 let successful = Arc::new(AtomicUsize::new(0));
795 let panic_once = context
796 .branch()
797 .to(Box::new(PanicOnceSink {
798 panicked: false,
799 successful: successful.clone(),
800 pp_log: element_pp_log(ElementType::Other, "panic-once", None),
801 }))
802 .unwrap();
803 let tee_branch = TeeBuilder::new("tee", context.clone())
804 .branch(panic_once)
805 .build()
806 .unwrap();
807 let mut upstream = SrcPad::new("source_src");
808 context.attach_pad(&mut upstream, tee_branch).unwrap();
809
810 let first = catch_unwind(AssertUnwindSafe(|| upstream.push(packet())));
811 assert!(
812 first.is_err(),
813 "the original downstream panic must propagate"
814 );
815 upstream
816 .push(packet())
817 .expect("the poisoned branch pad should be recovered on the next push");
818 assert_eq!(successful.load(Ordering::SeqCst), 1);
819 }
820
821 #[test]
822 fn a_poisoned_branch_list_does_not_break_attach_or_detach() {
823 let (bus, _bus_rx) = Bus::new();
824 let graph = PipelineGraph::new();
825 let source_id = graph.add_source(ElementType::Other, "source".into());
826 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
827 let (tee_branch, handle) = TeeBuilder::new("tee", context.clone())
828 .build_dynamic()
829 .unwrap();
830 let mut upstream = SrcPad::new("source_src");
831 context.attach_pad(&mut upstream, tee_branch).unwrap();
832 let shared = handle.shared.upgrade().unwrap();
833
834 let poisoned = catch_unwind(AssertUnwindSafe(|| {
835 let _branches = shared.branches.lock().unwrap();
836 panic!("poison the branch-list lock");
837 }));
838 assert!(poisoned.is_err());
839 assert_eq!(handle.sink_count(), 0);
840
841 let branch = handle
842 .branch()
843 .unwrap()
844 .to(Box::new(CountingSink {
845 name: "after-poison",
846 count: Arc::new(AtomicUsize::new(0)),
847 pp_log: element_pp_log(ElementType::Other, "after-poison", None),
848 }))
849 .unwrap();
850 let branch_id = handle.attach(branch).unwrap();
851 assert_eq!(handle.sink_count(), 1);
852 handle.detach(branch_id).unwrap();
853 assert_eq!(handle.sink_count(), 0);
854 }
855
856 #[test]
857 fn blocked_downstream_does_not_block_unrelated_attach_or_detach() {
858 let (bus, _bus_rx) = Bus::new();
859 let graph = PipelineGraph::new();
860 let source_id = graph.add_source(ElementType::Other, "source".into());
861 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
862 let (tee_branch, handle) = TeeBuilder::new("tee", context.clone())
863 .build_dynamic()
864 .unwrap();
865 let mut upstream = SrcPad::new("source_src");
866 context.attach_pad(&mut upstream, tee_branch).unwrap();
867
868 let (entered_tx, entered_rx) = mpsc::channel();
869 let (release_tx, release_rx) = mpsc::channel();
870 let blocking = handle
871 .branch()
872 .unwrap()
873 .to(Box::new(BlockingSink {
874 entered: Some(entered_tx),
875 release: release_rx,
876 pp_log: element_pp_log(ElementType::Other, "blocking", None),
877 }))
878 .unwrap();
879 let blocking_id = handle.attach(blocking).unwrap();
880
881 let push_thread = thread::spawn(move || upstream.push(packet()));
882 entered_rx
883 .recv_timeout(Duration::from_secs(1))
884 .expect("blocking branch was never entered");
885
886 let new_branch = handle
887 .branch()
888 .unwrap()
889 .to(Box::new(CountingSink {
890 name: "new",
891 count: Arc::new(AtomicUsize::new(0)),
892 pp_log: element_pp_log(ElementType::Other, "new", None),
893 }))
894 .unwrap();
895 let (attach_tx, attach_rx) = mpsc::channel();
896 let attach_handle = handle.clone();
897 let attach_thread = thread::spawn(move || {
898 let _ = attach_tx.send(attach_handle.attach(new_branch));
899 });
900
901 let (detach_tx, detach_rx) = mpsc::channel();
902 let detach_handle = handle.clone();
903 let detach_thread = thread::spawn(move || {
904 let _ = detach_tx.send(detach_handle.detach(blocking_id));
905 });
906
907 let attach_before_release = attach_rx.recv_timeout(Duration::from_millis(250)).ok();
908 let detach_before_release = detach_rx.recv_timeout(Duration::from_millis(250)).ok();
909 let attach_completed_while_blocked = attach_before_release.is_some();
910 let detach_completed_while_blocked = detach_before_release.is_some();
911 let _ = release_tx.send(());
912
913 push_thread.join().unwrap().unwrap();
914 attach_thread.join().unwrap();
915 detach_thread.join().unwrap();
916 let attach_result = attach_before_release
917 .unwrap_or_else(|| attach_rx.recv_timeout(Duration::from_secs(1)).unwrap());
918 let detach_result = detach_before_release
919 .unwrap_or_else(|| detach_rx.recv_timeout(Duration::from_secs(1)).unwrap());
920 attach_result.unwrap();
921 detach_result.unwrap();
922
923 assert!(
924 attach_completed_while_blocked,
925 "an unrelated attach waited for the blocked downstream"
926 );
927 assert!(
928 detach_completed_while_blocked,
929 "detach waited for an already-running downstream call"
930 );
931 }
932
933 #[test]
934 fn concurrent_push_attach_and_detach_stays_consistent_under_stress() {
935 const MIN_PUSHES: usize = 10_000;
936 const MUTATIONS: usize = 500;
937
938 let (bus, _bus_rx) = Bus::new();
939 let graph = PipelineGraph::new();
940 let source_id = graph.add_source(ElementType::Other, "source".into());
941 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
942 let initial_count = Arc::new(AtomicUsize::new(0));
943 let initial = context
944 .branch()
945 .to(Box::new(CountingSink {
946 name: "initial",
947 count: initial_count.clone(),
948 pp_log: element_pp_log(ElementType::Other, "initial", None),
949 }))
950 .unwrap();
951 let (tee_branch, handle) = TeeBuilder::new("tee", context.clone())
952 .branch(initial)
953 .build_dynamic()
954 .unwrap();
955 let mut upstream = SrcPad::new("source_src");
956 context.attach_pad(&mut upstream, tee_branch).unwrap();
957
958 let start = Arc::new(Barrier::new(2));
959 let mutating = Arc::new(AtomicBool::new(true));
960 let (push_done_tx, push_done_rx) = mpsc::channel();
961 let push_start = start.clone();
962 let push_mutating = mutating.clone();
963 let push_thread = thread::spawn(move || {
964 push_start.wait();
965 let packet = packet();
966 let mut pushed = 0;
967 let mut outcome = Ok(());
968 while push_mutating.load(Ordering::Acquire) || pushed < MIN_PUSHES {
969 if let Err(error) = upstream.push(packet.clone()) {
970 outcome = Err(error.to_string());
971 break;
972 }
973 pushed += 1;
974 if pushed % 32 == 0 {
975 thread::yield_now();
976 }
977 }
978 let _ = push_done_tx.send((upstream, outcome, pushed));
979 });
980
981 let (mutation_done_tx, mutation_done_rx) = mpsc::channel();
982 let mutation_start = start;
983 let mutation_handle = handle.clone();
984 let mutation_thread = thread::spawn(move || {
985 mutation_start.wait();
986 let outcome = (|| -> std::result::Result<(), String> {
987 for _ in 0..MUTATIONS {
988 let branch = mutation_handle
989 .branch()
990 .ok_or_else(|| "Tee disappeared during stress test".to_owned())?
991 .to(Box::new(CountingSink {
992 name: "dynamic",
993 count: Arc::new(AtomicUsize::new(0)),
994 pp_log: element_pp_log(ElementType::Other, "dynamic", None),
995 }))
996 .map_err(|error| error.to_string())?;
997 let branch_id = mutation_handle
998 .attach(branch)
999 .map_err(|error| error.to_string())?;
1000 thread::yield_now();
1001 mutation_handle
1002 .detach(branch_id)
1003 .map_err(|error| error.to_string())?;
1004 }
1005 Ok(())
1006 })();
1007 mutating.store(false, Ordering::Release);
1008 let _ = mutation_done_tx.send(outcome);
1009 });
1010
1011 mutation_done_rx
1012 .recv_timeout(Duration::from_secs(10))
1013 .expect("attach/detach stress thread timed out")
1014 .expect("attach/detach stress thread failed");
1015 let (upstream, push_outcome, pushed) = push_done_rx
1016 .recv_timeout(Duration::from_secs(10))
1017 .expect("push stress thread timed out");
1018 push_outcome.expect("push stress thread failed");
1019 push_thread.join().unwrap();
1020 mutation_thread.join().unwrap();
1021
1022 assert!(pushed >= MIN_PUSHES);
1023 assert_eq!(initial_count.load(Ordering::SeqCst), pushed);
1024 assert_eq!(handle.sink_count(), 1);
1025 let graph = context.graph.snapshot();
1026 assert_eq!(graph.nodes.len(), 3);
1027 assert_eq!(graph.edges.len(), 2);
1028 assert_eq!(graph.revision, 2 + (MUTATIONS as u64 * 2));
1029 drop(upstream);
1030 }
1031
1032 #[test]
1033 fn detached_sink_is_dropped_outside_the_graph_lock() {
1034 let (bus, _bus_rx) = Bus::new();
1035 let graph = PipelineGraph::new();
1036 let source_id = graph.add_source(ElementType::Other, "source".into());
1037 let context = Arc::new(Context::for_test(bus, "test", graph.clone(), source_id));
1038 let (tee_branch, handle) = TeeBuilder::new("tee", context.clone())
1039 .build_dynamic()
1040 .unwrap();
1041 let mut upstream = SrcPad::new("source_src");
1042 context.attach_pad(&mut upstream, tee_branch).unwrap();
1043
1044 let (dropped_tx, dropped_rx) = mpsc::channel();
1045 let branch = handle
1046 .branch()
1047 .unwrap()
1048 .to(Box::new(GraphInspectingDropSink {
1049 graph,
1050 dropped: Some(dropped_tx),
1051 pp_log: element_pp_log(ElementType::Other, "graph-inspecting-drop", None),
1052 }))
1053 .unwrap();
1054 let branch_id = handle.attach(branch).unwrap();
1055 let (done_tx, done_rx) = mpsc::channel();
1056 let detach_thread = thread::spawn(move || {
1057 let _ = done_tx.send(handle.detach(branch_id));
1058 });
1059
1060 dropped_rx
1061 .recv_timeout(Duration::from_secs(1))
1062 .expect("sink Drop deadlocked while inspecting the graph");
1063 done_rx
1064 .recv_timeout(Duration::from_secs(1))
1065 .expect("detach did not finish")
1066 .unwrap();
1067 detach_thread.join().unwrap();
1068 drop(upstream);
1069 }
1070
1071 #[test]
1072 fn retained_handle_does_not_keep_tee_context_or_bus_alive() {
1073 let (bus, bus_rx) = Bus::new();
1074 let graph = PipelineGraph::new();
1075 let source_id = graph.add_source(ElementType::Other, "source".into());
1076 let context = Arc::new(Context::for_test(bus, "test", graph, source_id));
1077 let (tee_branch, handle) = TeeBuilder::new("tee", context.clone())
1078 .build_dynamic()
1079 .unwrap();
1080
1081 drop(context);
1082 drop(tee_branch);
1083
1084 assert!(handle.branch().is_none());
1085 assert_eq!(handle.sink_count(), 0);
1086 assert!(
1087 bus_rx.iter().next().is_none(),
1088 "a retained TeeHandle must not keep the Bus sender alive"
1089 );
1090 }
1091}